/*
 * Copyright (c) 2013-2018 上海汇数数据. All rights reserved.
 * @(#) HashKt.java 2018-08-05 22:41
 */

package cn.jh.common.core.base;

import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;

import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import static java.nio.charset.StandardCharsets.UTF_8;


/**
 * Hash 算法工具。支持{@code MD5, SHA-1, SHA-256, SHA-384, SHA-512}
 *
 * @author Fuchun
 * @since 1.0
 */
public class HashKt {

    static final String MD5 = "MD5";
    static final String SHA1 = "SHA-1";
    static final String SHA_256 = "SHA-256";
    static final String SHA_384 = "SHA-384";
    static final String SHA_512 = "SHA-512";

    static final byte[] NULL_OR_EMPTY_MD5_HASH;
    static final byte[] NULL_OR_EMPTY_SHA1_HASH;
    static final byte[] NULL_OR_EMPTY_SHA256_HASH;
    static final byte[] NULL_OR_EMPTY_SHA384_HASH;
    static final byte[] NULL_OR_EMPTY_SHA512_HASH;
    static final String NULL_OR_EMPTY_MD5_HASH_HEX;
    static final String NULL_OR_EMPTY_SHA1_HASH_HEX;
    static final String NULL_OR_EMPTY_SHA256_HASH_HEX;
    static final String NULL_OR_EMPTY_SHA384_HASH_HEX;
    static final String NULL_OR_EMPTY_SHA512_HASH_HEX;

    static {
        byte[] emptyBytes = new byte[0];
        byte[] md5 = hash(MD5, emptyBytes);
        byte[] sha1 = hash(SHA1, emptyBytes);
        byte[] sha256 = hash(SHA_256, emptyBytes);
        byte[] sha384 = hash(SHA_384, emptyBytes);
        byte[] sha512 = hash(SHA_512, emptyBytes);

        NULL_OR_EMPTY_MD5_HASH = md5;
        NULL_OR_EMPTY_SHA1_HASH = sha1;
        NULL_OR_EMPTY_SHA256_HASH = sha256;
        NULL_OR_EMPTY_SHA384_HASH = sha384;
        NULL_OR_EMPTY_SHA512_HASH = sha512;

        NULL_OR_EMPTY_MD5_HASH_HEX = HexKt.encode(md5);
        NULL_OR_EMPTY_SHA1_HASH_HEX = HexKt.encode(sha1);
        NULL_OR_EMPTY_SHA256_HASH_HEX = HexKt.encode(sha256);
        NULL_OR_EMPTY_SHA384_HASH_HEX = HexKt.encode(sha384);
        NULL_OR_EMPTY_SHA512_HASH_HEX = HexKt.encode(sha512);
    }

    @Contract(value = "!null -> param1", pure = true)
    private static Charset getCharset(@Nullable Charset c) {
        return c == null ? UTF_8 : c;
    }

    private static boolean isNullOrEmpty(byte[] data) {
        return data == null || data.length == 0;
    }

    public static byte[] md5(byte[] data) {
        if (isNullOrEmpty(data)) {
            return NULL_OR_EMPTY_MD5_HASH;
        }
        return hash(MD5, data);
    }

    public static String md5Hex(byte[] data) {
        if (isNullOrEmpty(data)) {
            return NULL_OR_EMPTY_MD5_HASH_HEX;
        }
        return HexKt.encode(md5(data));
    }

    public static String md5Hex(String src, Charset charset) {
        if (Strings.isNullOrEmpty(src)) {
            return NULL_OR_EMPTY_MD5_HASH_HEX;
        }
        return md5Hex(src.getBytes(getCharset(charset)));
    }

    public static String md5Hex(String src) {
        return md5Hex(src, UTF_8);
    }

    public static byte[] sha1(byte[] data) {
        if (isNullOrEmpty(data)) {
            return NULL_OR_EMPTY_SHA1_HASH;
        }
        return hash(SHA1, data);
    }

    public static String sha1Hex(byte[] data) {
        if (isNullOrEmpty(data)) {
            return NULL_OR_EMPTY_SHA1_HASH_HEX;
        }
        return HexKt.encode(sha1(data));
    }

    public static String sha1Hex(String src, Charset charset) {
        if (Strings.isNullOrEmpty(src)) {
            return NULL_OR_EMPTY_SHA1_HASH_HEX;
        }
        return sha1Hex(src.getBytes(charset));
    }

    public static String sha1Hex(String src) {
        return sha1Hex(src, UTF_8);
    }

    public static byte[] sha256(byte[] data) {
        if (isNullOrEmpty(data)) {
            return NULL_OR_EMPTY_SHA256_HASH;
        }
        return hash(SHA_256, data);
    }

    public static String sha256Hex(byte[] data) {
        if (isNullOrEmpty(data)) {
            return NULL_OR_EMPTY_SHA256_HASH_HEX;
        }
        return HexKt.encode(sha256(data));
    }

    public static String sha256Hex(String src, Charset charset) {
        if (Strings.isNullOrEmpty(src)) {
            return NULL_OR_EMPTY_SHA256_HASH_HEX;
        }
        return sha256Hex(src.getBytes(charset));
    }

    public static String sha256Hex(String src) {
        return sha256Hex(src, UTF_8);
    }

    public static byte[] sha384(byte[] data) {
        if (isNullOrEmpty(data)) {
            return NULL_OR_EMPTY_SHA384_HASH;
        }
        return hash(SHA_384, data);
    }

    public static String sha384Hex(byte[] data) {
        if (isNullOrEmpty(data)) {
            return NULL_OR_EMPTY_SHA384_HASH_HEX;
        }
        return HexKt.encode(sha384(data));
    }

    public static String sha384Hex(String src, Charset charset) {
        if (Strings.isNullOrEmpty(src)) {
            return NULL_OR_EMPTY_SHA384_HASH_HEX;
        }
        return sha384Hex(src.getBytes(charset));
    }

    public static String sha384Hex(String src) {
        return sha384Hex(src, UTF_8);
    }

    public static byte[] sha512(byte[] data) {
        if (isNullOrEmpty(data)) {
            return NULL_OR_EMPTY_SHA512_HASH;
        }
        return hash(SHA_512, data);
    }

    public static String sha512Hex(byte[] data) {
        if (isNullOrEmpty(data)) {
            return NULL_OR_EMPTY_SHA512_HASH_HEX;
        }
        return HexKt.encode(sha512(data));
    }

    public static String sha512Hex(String src, Charset charset) {
        if (Strings.isNullOrEmpty(src)) {
            return NULL_OR_EMPTY_SHA512_HASH_HEX;
        }
        return sha512Hex(src.getBytes(charset));
    }

    public static String sha512Hex(String src) {
        return sha512Hex(src, UTF_8);
    }

    static byte[] hash(String algorithm, byte[] data) {
        try {
            MessageDigest digest = MessageDigest.getInstance(algorithm);
            digest.update(data);
            return digest.digest();
        } catch (NoSuchAlgorithmException ex) {
            throw new IllegalArgumentException(ex);
        }
    }
}
